﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Demo_SteeringBehaviour
{
    public abstract class Agent
    {
        #region DATA_MEMBERS
        //
        #region PRIMARY_SETUP_OF_OBJECT (Minimal)
        //
        protected Texture2D texture;            //D001: Texture of agent
        protected Vector2 position;             //D002: Position of agent (Top-Left)
        protected Vector2 size;                 //D003: Size of agent (x-Width, y-Height)
        protected Vector2 center;               //D004: Center of agent
        protected bool isAlive;                 //D005: Used to track dead agent AND decide IF agent should be Rendered
        protected agent_Type agentType;         //D006: Track species of Agent
        //
        #endregion PRIMARY_SETUP_OF_OBJECT (Minimal)

        #region Movement_DIRECTION_N_ANGLE
        //
        protected float angleInRadians;         //D007: Angle in Radians, direction object 'IS/SHOULD' move 
        protected Vector2 direction;            //D008: Normalized obj direction (theta/angle: converted into Vector)
        //Controls: manage direction|angle updates
        private const float pauseTime = 150f;   //D009: Time for how long agent should remain paused, before moving again
        protected float pauseCounter;           //D010: Counter to track PAUSE duration
        protected bool isPaused;                //D011: Flag to trigger PAUSE | PLAY of agent movement
        //
        #endregion Movement_DIRECTION_N_ANGLE

        #region CONTROL_ROTATION_MOVEMENT_SPEEDs
        //
        //Control: ROTATION SPEED
        protected float rotation_Normal;        //D012: Speed obj should rotate (in radians)
        protected float rotation_Max;           //D013: Rotation speed: MAX Limit
        
        //Control: MOVEMENT SPEED
        protected float speed_Normal;           //D014: Speed obj should be moving per frame
        protected float speed_Max;              //D015: Obj speed: MAX Limit
        protected float speed_Current;          //D016: Current Speed of Agent
        //
        #endregion CONTROL_ROTATION_MOVEMENT_SPEEDs

        #region STEERING_BEHAVIOUR
        //
        protected SteeringBehaviour behaviour;  //D017: Behaviour of Agent
        protected behaviour_Type behaviourType; //D018: Track agent's current behaviour
        //
        #endregion STEERING_BEHAVIOUR

        #region CONTROL_WANDER_BEHAVIOUR
        //
        protected float wDistance;              //D019: Magnitude from Agent->Center to wanderCircle->Center
        protected float wRadius;                //D020: Parameter Boundary (radius) for wonderTarget->Position
        protected float wCounter = 0.0f;        //D021: Counter to trigger WANDER Update
        protected float wTime;                  //D022: Time for how long WANDER update gets delayed per # of frames
        //
        #endregion CONTROL_WANDER_BEHAVIOUR

        #region CONTROL_FLEE_BEHAVIOUR
        //
        protected float flee_PanicDistance;     //D021: Displacement check  for 'FleePanic' Steering Behaviour 
        protected float flee_CautionDistance;   //D022: Displacement check  for 'FleeCaution' Steering Behaviour
        protected float flee_CapturetDistance;  //D023 : Displacement check  for 'Captured'
        protected float fleeCounter;            //D024: Counter to trigger ESCAPE in Flee Mode
        protected float fleeTime;               //D025: Trigger ESCAPE: 'fleeCounter >= fleeTime'
        //
        #endregion CONTROL_FLEE_BEHAVIOUR

        #region CONTROL_SEEK_BEHAVIOUR
        //
        protected float seek_Distance;        //D026: Displacement check  for 'Seek' Steering Behaviour
        protected float seekCounter;          //D027: Counter to trigger STOP Seek Mode
        protected float seekTime;             //D028: Trigger STOP Seek Mode: 'seekCounter >= seekTime'
        //
        #endregion CONTROL_FLEE_BEHAVIOUR
        //
        #endregion DATA_MEMBERS



        #region OBJECT_CONSTRUCTION
        //
        //C001: DEFAULT CONSTRUCTOR
        public Agent(Texture2D texture)
        {
            this.texture = texture;
            this.position = Helper.Position_Random(20, 780, 20, 580);
            this.size = new Vector2(16.0f);
            //this.agentType = agent_Type.NEUTRAL;

            this.wTime = 120.0f;
            this.wCounter = 0.0f;

            Init();
        }

        //C002: OVERLOADED CONSTRUCTOR
        ////public Agent(Texture2D texture, Vector2 position, Vector2 size, agent_Type agentType)
        ////{
        ////    this.texture = texture;
        ////    this.position = position;
        ////    this.size = size;
        ////    this.agentType = agentType;

        ////    Init();
        ////}
        ////!

        //C003: Init data members...
        protected abstract void Init();
        ////protected void Init()
        ////{
        ////    #region Movement_Direction
        ////    //Movement Direction/Angle of obj
        ////    this.angleInRadians = Helper.AngleInRadians_Random();   //Random direction obj moves, in Radians
        ////    this.direction = Helper.Radian2Vector(angleInRadians);  //Direction obj moves, Convert angleInRadians to Vector2

        ////    //Pausing the Agent Controls
        ////    this.pauseCounter = 0.0f;
        ////    this.isPaused = false;
            
        ////    //Center is based on which direction obj is pointing
        ////    this.center = this.position + Helper.Inverse(this.direction * (this.size / 2));
        ////    this.isAlive = true;
        ////    #endregion

        ////    #region Rotation/Movement Speeds
        ////    //Rotation Speed Controls
        ////    this.rotation_Normal = 0.17f;   //Normal Rotating speed in radians (~10-degrees)
        ////    this.rotation_Max = 0.35f;      //MAX Rotating speed in radians (~20-degrees)

        ////    //Movement Speed Controls
        ////    this.speed_Normal = 0.50f;      //Normal Movement speed of obj
        ////    this.speed_Max = 1.2f;          //MAX Movement speed of obj
        ////    #endregion

        ////    #region Steering_Behaviour
        ////    this.behaviour = null;
        ////    this.behaviourType = behaviour_Type.NEUTRAL;
        ////    #endregion

        ////    #region Wander_Behaviour
        ////    this.wDistance = (float)Math.Round(this.size.X * 2, 2);
        ////    this.wRadius = (float)Math.Round(this.size.X / 2, 2);
        ////    #endregion

        ////    #region Flee_Behaviour
        ////    this.flee_PanicDistance = (float)Math.Round(this.size.X * 1.5, 2);
        ////    this.flee_CautionDistance = (float)Math.Round(this.size.X * 3, 2);
        ////    this.flee_CapturetDistance = (float)Math.Round(this.size.X / 2, 2);
        ////    this.fleeCounter = 0.0f;
        ////    this.fleeTime = 150f;
        ////    #endregion

        ////    #region Seek_Behaviour
        ////    this.seek_Distance = (float)Math.Round(this.size.X * 3, 2);
        ////    this.seekCounter = 0.0f;
        ////    this.seekTime = 180f;
        ////    #endregion
        ////}
        //
        #endregion OBJECT_CONSTRUCTION



        #region PROPERTIES
        //
        #region PRIMARY_SETUP_OF_OBJECT (Minimal)
        //
        public Texture2D Texture
        {
            get { return this.texture; }
            set { this.texture = value; }
        }
        //
        public Vector2 Position
        {
            get { return this.position; }
            set
            {
                this.position = value;
                this.position.X = (float)Math.Round(this.position.X, 2);
                this.position.Y = (float)Math.Round(this.position.Y, 2);

                //Center is calculated based on INVERSED direction of obj 
                this.center = this.position + Helper.Inverse(this.direction * (this.size / 2));
                this.center.X = (float)Math.Round(this.center.X, 2);
                this.center.Y = (float)Math.Round(this.center.Y, 2);
            }
        }
        //
        public Vector2 Size
        {
            get { return this.size; }
            set { this.size = value; }
        }
        //Origin used to rotate obj 
        public Vector2 Center
        {
            get { return this.center; }
            //set { center = value; }
        }
        //
        public bool IsAlive
        {
            get { return this.isAlive; }
            set { this.isAlive = value; }
        }
        //
        public agent_Type AgentType
        {
            get { return this.agentType; }
            set { this.agentType = value; }
        }
        #endregion

        #region Movement_DIRECTION_N_ANGLE
        //
        //Tracks direction obj is moving in Radians
        //Input: angle in radians
        public float AngleInRadians
        {
            get { return this.angleInRadians; }
            set
            {
                //This ensures  rotation theta does not continue to grow and grow forever in +ve direction
                if (value > (float)Math.Round((float)Math.PI * 2, 2))
                {
                    this.angleInRadians = (float)Math.Round(value - Math.PI * 2, 2);
                }
                //This ensures  rotation theta does not continue to grow and grow forever in -ve direction
                else if (value < (float)Math.Round((float)Math.PI * -2, 2))
                {
                    this.angleInRadians = (float)Math.Round(value + Math.PI * 2, 2);
                }
                else { this.angleInRadians = (float)Math.Round(value, 2); }

                //this.angleInRadians = value;
                this.direction = Helper.Radian2Vector(this.angleInRadians);
            }
        }
        //Direction obj is moving in Vector form
        public Vector2 Direction
        {
            get { return this.direction; }
        }
        //
        #endregion Movement_DIRECTION_N_ANGLE

        #region PAUSE_PLAY_AGENT
        //
        public float PauseTime
        {
            get { return pauseTime; }
        }
        //
        public float PauseCounter
        {
            get { return this.pauseCounter; }
            set { this.pauseCounter = value; }
        }
        //
        public bool IsPaused
        {
            get { return this.isPaused; }
            set { this.isPaused = value; }
        }
        //
        #endregion PAUSE_PLAY_AGENT

        #region ROTATION_MOVEMENT_SPEEDs
        //
        public float Rotation_Normal
        {
            get { return this.rotation_Normal; }
            set { this.rotation_Normal = value; }
        }
        //
        public float Rotation_Max
        {
            get { return this.rotation_Max; }
            set { this.rotation_Max = value; }
        }
        //
        public float Speed_Normal
        {
            get { return this.speed_Normal; }
            set { this.speed_Normal = value; }
        }
        //
        public float Speed_Max
        {
            get { return this.speed_Max; }
            set { this.speed_Max = value; }
        }
        //
        public float Speed_Current
        {
            get { return this.speed_Current; }
            set { this.speed_Current = value; }
        }
        //
        #endregion ROTATION_MOVEMENT_SPEEDs

        public behaviour_Type Behaviour
        {
            get { return this.behaviourType; }
            //set { this.behaviourType = value; }
        }

        #region WANDER_BEHAVIOUR
        //Wander Distance
        public float WanderDistance
        {
            get { return this.wDistance; }
            set { this.wDistance = value; }
        }
        //Wander Distance
        public float WanderRadius
        {
            get { return this.wRadius; }
            set { this.wRadius = value; }
        }
        //
        public float WanderCounter
        {
            get { return this.wCounter; }
            set { this.wCounter = value; }
        }
        //
        public float WanderTime
        {
            get { return this.wTime; }
            set { this.wTime = value; }
        }
        #endregion

        #region FLEE_BEHAVIOUR
        //
        public float Flee_Panic_Distance
        {
            get { return this.flee_PanicDistance; }
            set { this.flee_PanicDistance = value; }
        }
        //
        public float Flee_Caution_Distance
        {
            get { return this.flee_CautionDistance; }
            set { this.flee_CautionDistance = value; }
        }
        //
        public float Flee_Capture_Distance
        {
            get { return this.flee_CapturetDistance; }
            set { this.flee_CapturetDistance = value; }
        }
        //
        public float FleeCounter
        {
            get { return this.fleeCounter; }
            set { this.fleeCounter = value; }
        }
        //
        public float FleeTime
        {
            get { return this.fleeTime; }
            set { this.fleeTime = value; }
        }
        #endregion

        #region SEEK_BEHAVIOUR
        public float SeekDistance
        {
            get { return this.seek_Distance; }
            set { this.seek_Distance = value; }
        }
        //
        public float SeekCounter
        {
            get { return this.seekCounter; }
            set { this.seekCounter = value; }
        }
        //
        public float SeekTime
        {
            get { return this.seekTime; }
            set { this.seekTime = value; }
        }
        #endregion
        //
        #endregion PROPERTIES


        //Update angleInRadians and direction if agent is colliding with window boundaries
        //Called inside Update_Behaviour()
        protected virtual void BoundaryCheck(Vector2 moveDirection)
        {
            Vector2 temp = this.position;
            //!do boundary check...And wrap around the screen...
            float left = -this.size.X,  right  = 800.0f;
            float top  = -this.size.Y,  bottom = 600.0f;

            //LEFT BOUND check....X-Position
            if (this.position.X < left)
            {
                temp.X = right;  Position = temp;
            }
            //RIGHT BOUND check....X-Position
            if (this.position.X > right)
            {
                temp.X = left;  Position = temp;
            }
            //TOP BOUND check....Y-Position
            if (this.position.Y < top)
            {
                temp.Y = bottom;  Position = temp;
            }
            //BOTTOM BOUND check....Y-Position
            if (this.position.Y > bottom)
            {
                temp.Y = top;  Position = temp;
            }
            //STILL INSIDE SCREEN....continue on its path...
           Position += moveDirection;
        }

        protected virtual void Update_Behaviour(GameTime gameTime, List<Agent> agents)
        {
            if (behaviour == null)
            {
                this.behaviour = new SteeringBehaviour();
            }
            this.behaviourType = behaviour.SelectBehaviour(this, agents);

            //TODO: CON-D from here....
            switch ((int)behaviourType)
            {
                case (int)behaviour_Type.WANDER:
                    AngleInRadians += behaviour.Wander(this);
                    this.speed_Current = this.speed_Normal;
                    break;
                case (int)behaviour_Type.FLEE_PANIC:
                    AngleInRadians = behaviour.Flee_Panic(this, this.behaviour.AgentList);
                    this.speed_Current = this.speed_Max;
                    break;
                case (int)behaviour_Type.FLEE_CAUTION:
                    AngleInRadians = behaviour.Flee_Caution(this, this.behaviour.AgentList);
                    this.speed_Current = this.speed_Normal;
                    break;
                case (int)behaviour_Type.SEEK:
                    AngleInRadians = behaviour.Seek(this, this.behaviour.AgentList);
                    this.speed_Current = this.speed_Max;
                    break;
                case (int)behaviour_Type.CAUGHT:
                    AngleInRadians = 0.0f;
                    this.speed_Current = 0.0f;
                    this.isAlive = false;
                    this.isPaused = true;
                    //?Spawn();
                    break;
            }

            //Vector2 temp = new Vector2(this.direction.X * this.speed_Current, this.direction.Y * this.speed_Current);
            //TODO: BoundaryWrap....
            //Position += temp;
            //return temp;
        }

        #region ABSTRACT_METHODS
        //
        //TODO: INHERITED CLASS will Implement this Method
        protected abstract void Spawn();
        
        //TODO: INHERITED CLASS will Implement this Method
        //caught timer for reSpawn
        public abstract void Update(GameTime gameTime, List<Agent> agents);

        //
        public abstract void Draw(SpriteBatch spriteBatch);
        //
        #endregion
    }//class
}//namespace